package com.edt.ichannel.utils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.*;

public class SignUtil {
	
	public static String encodingCharset = "utf-8";
	private static final String ALG_MD5 = "MD5";
	private static final String ALG_SHA1 = "SHA-1";
	

	/** WWW网关签名原文 */
	private static final String SING_KEY = "unicom&nantian&webbankdeposit";
	
	private static Log logger = LogFactory.getLog(SignUtil.class);
	
	static char hexdigits[] = {'0','1','2','3','4','5','6','7','8','9', 'a', 'b', 'c', 'd', 'e', 'f' };
	/**
	 * 测试签名
	 * SmerNo 商户号
	 * map 报文参数map
	 * charSet 编码格式  默认GBK
	 * SignType 加密方式 默认MD5
	 */
	public static String testSingn(String SmerNo, Map<String, String> map, String charSet, String SignType){
		if("".equalsIgnoreCase(charSet)||charSet==null){
			charSet="UTF-8";
		}
		if("".equalsIgnoreCase(SignType)||SignType==null){
			SignType="MD5";
		}
		String key=ConfigInfo.SingleIssueKey ;
		Connection conn = null;
		Statement stmt = null;
		ResultSet rs = null;
		String localMd5String="";
//		try {
//
//			Class.forName(PropertiesUtil.getValue("datasource.connection.driverclass"));// 实例化oracle数据库驱动程序(建立中间件)
//			String url = PropertiesUtil.getValue("datasource.connection.url");// @localhost为服务器名，sjzwish为数据库实例名
//			String username = PropertiesUtil.getValue("datasource.connection.username");
//			String password = PropertiesUtil.getValue("datasource.connection.password");
//			conn = DriverManager.getConnection(url, username, password);// 连接数据库，a代表帐户,a代表密码
//			stmt = conn.createStatement();// 提交sql语句,创建一个Statement对象来将SQL语句发送到数据库
//			// 查询数据用executeQuery
//			rs = stmt.executeQuery("select mersignpassword from UNIPAYUSER.T_PAY_MER_ENTERPRISE WHERE MERNO ="+SmerNo);// 执行查询,(ruby)为表名
////			Map<String,Object> resultmap=new HashMap<String, Object>();
//			while (rs.next()) {// 使当前记录指针定位到记录集的第一条记录
//				key=rs.getString("mersignpassword");
////				key2=rs.getString("mersignpassword2");
////				resultmap.put("mersignpassword", key);
////				resultmap.put("mersignpassword2", key2);
//				System.out.println("商户"+SmerNo+"--查询的key"+key);
//				
//			}// 1代表当前记录的第一个字段的值，可以写成字段名。
//				// 2代表当前记录的第二个字段的值，可以写成字段名。
////			Map map=方法(resultmap);
////			map.get("mersignpassword");
////			map.get("mersignpassword2");
//			
//			
//			if(key==null||"".equals(key)){
//				return "";
//			}else{
		
		
		
				 localMd5String = getHexSign(map, charSet, SignType, key);
//			}
//			// 添加数据用executeUpdate
//			// stmt.executeUpdate("insert into ss values(7,'张学友')");
//
//			// 修改数据用executeUpdate
//			// stmt.executeUpdate("update ss set name = '张曼玉' where id = 5");
//
//			// 删除 数据用executeUpdate
//			// stmt.executeUpdate("delete from ss where id = 6");
//			
//		} catch (SQLException e) {
//			e.printStackTrace();
//		} catch (ClassNotFoundException e) {
//			e.printStackTrace();
//		} finally {
//			try {
//				// 关闭数据库，结束进程
//				rs.close();
//				stmt.close();
//				conn.close();
//			} catch (SQLException e) {
//				e.printStackTrace();
//			}
//		}
//		
//		
				 
				 
				 
				 
		return localMd5String;
		
	}
	/**
	 * 测试签名
	 * SmerNo 商户号
	 * map 报文参数map
	 * charSet 编码格式  默认GBK
	 * SignType 加密方式 默认MD5
	 */
	public static String testSingn(String SmerNo, Map<String, String> map, String charSet, String SignType, String fileText){
		if("".equalsIgnoreCase(charSet)||charSet==null){
			charSet="UTF-8";
		}
		if("".equalsIgnoreCase(SignType)||SignType==null){
			SignType="MD5";
		}
		String key=ConfigInfo.SingleIssueKey ;
		Connection conn = null;
		Statement stmt = null;
		ResultSet rs = null;
		String localMd5String="";
		/*try {

			Class.forName(PropertiesUtil.getValue("datasource.connection.driverclass"));// 实例化oracle数据库驱动程序(建立中间件)
			String url = PropertiesUtil.getValue("datasource.connection.url");// @localhost为服务器名，sjzwish为数据库实例名
			String username = PropertiesUtil.getValue("datasource.connection.username");
			String password = PropertiesUtil.getValue("datasource.connection.password");
			conn = DriverManager.getConnection(url, username, password);// 连接数据库，a代表帐户,a代表密码
			stmt = conn.createStatement();// 提交sql语句,创建一个Statement对象来将SQL语句发送到数据库
			// 查询数据用executeQuery
			rs = stmt.executeQuery("select mersignpassword from UNIPAYUSER.T_PAY_MER_ENTERPRISE WHERE MERNO ="+SmerNo);// 执行查询,(ruby)为表名
			while (rs.next()) {// 使当前记录指针定位到记录集的第一条记录
				key=rs.getString("mersignpassword");
				System.out.println("商户"+SmerNo+"--查询的key"+key);
				
			}// 1代表当前记录的第一个字段的值，可以写成字段名。
				// 2代表当前记录的第二个字段的值，可以写成字段名。
			if(key==null||"".equals(key)){
				return null;
			}
			// 添加数据用executeUpdate
			// stmt.executeUpdate("insert into ss values(7,'张学友')");

			// 修改数据用executeUpdate
			// stmt.executeUpdate("update ss set name = '张曼玉' where id = 5");

			// 删除 数据用executeUpdate
			// stmt.executeUpdate("delete from ss where id = 6");
			 localMd5String = getFileTextSign(map,fileText,charSet, SignType, key);
		} catch (SQLException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} finally {
			try {
				// 关闭数据库，结束进程
				rs.close();
				stmt.close();
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		*/
		localMd5String = getFileTextSign(map,fileText,charSet, SignType, key);
		return localMd5String;
		
	}
	/**
	 * 执行商户信息签名
	 * @param source 签名数据
	 * @param signType 签名类型
	 * @param inputCharset 字符集
	 * @param key 密码
	 * @return
	 */
	public static String merSign(Map<String, String> source, String signType, String inputCharset, String key){
		logger.info("------------------网关加密开始----------------------------");
		logger.info("网关签名原文："+source);
		logger.info("网关以"+signType+"方式签名...");
		logger.info("签名原文字符集："+inputCharset);
//		logger.info("签名key："+key);
		
		String signResult = null;
		
		if(ESignType.SIGN_SHA.getCode().equals(signType)){
			//signResult = UniPaySignUtils.getSha1SignMsg(source, key, inputCharset);
		}else if(ESignType.SIGN_MD5.getCode().equals(signType)){
			//signResult = UniPaySignUtils.getMd5SignMsg(source, key, inputCharset);
		}else {
			logger.info("签名类型不正确:"+signType);
			signResult = null;
		}
		logger.info("生成的加密数据：" + signResult);
		logger.info("------------------网关加密结束----------------------------");
		return signResult;
	}
	
	/**
	 * 验签(商戶)
	 * @param signData：签名数据
	 * @param source：源数据
	 * @param key：签名密码原文
	 * @param signType：签名方式
	 * @return 验签结果
	 */
	public static boolean verifyMerSing(String signData, Map<String, String> source, String key, ESignType signType, String inputCharset) {
		String signResult = merSign(source, signType.getCode(), inputCharset, key);//针对报文计算签名数据
		if(signResult == null||!signData.equalsIgnoreCase(signResult)){//将报文发送来的签名数据和计算出的签名数据比较
			return false;
		}
		return true;
	}
	
	
	/**
	 * 对商户请求信息进行验签
	 * @param param
	 * @param charset
	 * @param algorithm
	 * @param signKey
	 * @return
	 */
	public static String getHexSign(Map<String, String> param, String charset, String algorithm, String signKey){
		StringBuilder sb = new StringBuilder();
		List<String> keys = new ArrayList<String>(param.keySet());
		Collections.sort(keys);
		for (int i = 0; i < keys.size(); i++) {
			String key = keys.get(i);
			String value = param.get(key);
			if (key.equalsIgnoreCase("cert") || key.equalsIgnoreCase("hmac")
					|| key.equalsIgnoreCase("signMsg") || value == null || value.length() == 0) {
				continue;
			}
//			if(!"listorderDetail".equalsIgnoreCase(key)){
				sb.append(key).append("=").append(value).append("|");
//			}
		}
		sb.append("key=").append(signKey);
		String md5Str = sb.toString();
		logger.info(" ************ MD5摘要数据原串【 " + md5Str.substring(0, md5Str.length() - 9) + "*********" + "】 ************ ");
		return getHexSign(sb.toString(), charset, algorithm, true);
	}
	
	/**
	 * 增加文件内容的签名
	 * @param param
	 * @param charset
	 * @param algorithm
	 * @param fileText 文件签名内容
	 * @return
	 */
	public static String getFileTextSign(Map<String, String> param, String md5file, String charset, String algorithm, String signKey){
		StringBuilder sb = new StringBuilder();
		logger.info("文件摘要内容"+md5file);
		param.put("fileAbstract", md5file);
		List<String> keys = new ArrayList<String>(param.keySet());
		Collections.sort(keys);
		for (int i = 0; i < keys.size(); i++) {
			String key = keys.get(i);
			String value = param.get(key);
			if (key.equalsIgnoreCase("cert") || key.equalsIgnoreCase("hmac")
					|| key.equalsIgnoreCase("signMsg") || value == null || value.length() == 0) {
				continue;
			}
			sb.append(key).append("=").append(value).append("|");
		}
		
		
		sb.append("key=").append(signKey);//拼接密钥
		String md5Str = sb.toString();
		logger.info(" ************ MD5带文件内容的摘要数据原串【 " + md5Str.substring(0, md5Str.length() - 9) + "*********" + "】 ************ ");
		return getHexSign(sb.toString(), charset, algorithm, true);
	}
	
	public static String getHexSign(String data, String charset, String algorithm, boolean toLowerCase){
	    char[] DIGITS_LOWER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
	    char[] DIGITS_UPPER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
	    //Used to build output as Hex
	    char[] DIGITS = toLowerCase ? DIGITS_LOWER : DIGITS_UPPER;
	    //get byte[] from {@link TradePortalUtil#getBytes(String, String)}
	    byte[] dataBytes = getBytes(data, charset);
		byte[] algorithmData = null;
		try {
			//get an algorithm digest instance
			algorithmData = MessageDigest.getInstance(algorithm).digest(dataBytes);
		} catch (NoSuchAlgorithmException e) {
			logger.error("签名字符串[" + data + "]时发生异常:System doesn't support this algorithm[" + algorithm + "]");
			return "";
		}
		char[] respData = new char[algorithmData.length << 1];
		//two characters form the hex value
		for (int i = 0, j = 0; i < algorithmData.length; i++) {
			respData[j++] = DIGITS[(0xF0 & algorithmData[i]) >>> 4];
			respData[j++] = DIGITS[0x0F & algorithmData[i]];
		}
		return new String(respData);
	}
	
	public static byte[] getBytes(String data, String charset) {
		data = (data == null ? "" : data);
		if (isEmpty(charset)) {
			return data.getBytes();
		}
		try {
			return data.getBytes(charset);
		} catch (UnsupportedEncodingException e) {
			logger.error("将字符串[" + data + "]转为byte[]时发生异常:系统不支持该字符集[" + charset + "]");
			return data.getBytes();
		}
	}
	
	public static boolean isEmpty(String input) {
		return null == input || 0 == input.length() || 0 == input.replaceAll("\\s", "").length();
	}
	
	/**
	 * 通过ASCII码将十六进制的字节数组格式化为十六进制字符串
	 * @see 使用说明详见<code>formatToHexStringWithASCII(byte[], int, int)</code>方法
	 */
	public static String formatHexToHexStringWithASCII(byte[] hexData, int offset, int length){
		byte[] data = new byte[hexData.length];
		for (int i = 0; i < data.length; i++) {
			data[i] = Integer.valueOf(""+hexData[i], 16).byteValue(); //获取16进制数的ASCII值,比如16进制的41对应ASCII的65
		}
		return formatToHexStringWithASCII(data, offset, length);
	}
	
	
	/**
	 * 通过ASCII码将十进制的字节数组格式化为十六进制字符串
	 * @see 该方法会将字节数组中的所有字节均格式化为字符串
	 * @see 使用说明详见<code>formatToHexStringWithASCII(byte[], int, int)</code>方法
	 */
	public static String formatToHexStringWithASCII(byte[] data){
		return formatToHexStringWithASCII(data, 0, data.length);
	}
	
	
	/**
	 * 通过ASCII码将十进制的字节数组格式化为十六进制字符串
	 * @see 该方法常用于字符串的十六进制打印,打印时左侧为十六进制数值,右侧为对应的字符串原文
	 * @see 在构造右侧的字符串原文时,该方法内部使用的是平台的默认字符集,来解码byte[]数组
	 * @see 该方法在将字节转为十六进制时,默认使用的是<code>java.util.Locale.getDefault()</code>
	 * @see 详见String.format( String , Object...)方法和new String(byte[], int, int)构造方法
	 * @param data   十进制的字节数组
	 * @param offset 数组下标,标记从数组的第几个字节开始格式化输出
	 * @param length 格式长度,其不得大于数组长度,否则抛出java.lang.ArrayIndexOutOfBoundsException
	 * @return 格式化后的十六进制字符串
	 */
	public static String formatToHexStringWithASCII(byte[] data, int offset, int length){
		int end = offset + length;
		StringBuilder sb = new StringBuilder();
		StringBuilder sb2 = new StringBuilder();
		sb.append("\r\n------------------------------------------------------------------------");
		boolean chineseCutFlag = false;
		for(int i=offset; i<end; i+=16){
			sb.append(String.format("\r\n%04X: ", i-offset)); //X或x表示将结果格式化为十六进制整数
			sb2.setLength(0);
			for(int j=i; j<i+16; j++){
				if(j < end){
					byte b = data[j];
					if(b >= 0){ //ENG ASCII
						sb.append(String.format("%02X ", b));
						if(b<32 || b>126){ //不可见字符
							sb2.append(" ");
						}else{
							sb2.append((char)b);
						}
					}else{ //CHA ASCII
						if(j == i+15){ //汉字前半个字节
							sb.append(String.format("%02X ", data[j]));
							chineseCutFlag = true;
							String s = new String(data, j, 2);
							sb2.append(s);
						}else if(j == i&&chineseCutFlag){ //后半个字节
							sb.append(String.format("%02X ", data[j]));
							chineseCutFlag = false;
							String s = new String(data, j, 1);
							sb2.append(s);
						}else{
							sb.append(String.format("%02X %02X ", data[j], data[j + 1]));
							String s = new String(data, j, 2);
							sb2.append(s);
							j++;
						}
					}
				}else{
					sb.append("   ");
				}
			}
			sb.append("| ");
			sb.append(sb2.toString());
		}
		sb.append("\r\n------------------------------------------------------------------------");
		return sb.toString();
	}
	
	public static void main(String[] args) {
		/*Map<String, String> params = new HashMap<String, String>();
		params.put("interfaceVersion", "1.0.0.0");
		params.put("amount", "10");
		params.put("orderNo", "2011090100000001");
		params.put("orderDate", "2011090100000001");
		params.put("tranrst", "1");
		params.put("userNo", "20110901000000011234");
		params.put("respCode", "99999999");
		params.put("errDis", "123321123");
		params.put("signType", "MD5");
		
		System.out.println("网关签名数据："+SignUtil.perSign(params));
		System.out.println("验证充值签名："+SignUtil.perVerify("39417fdecf5fd7d647a0adfce8a9fa9c", params));*/
		StringBuffer tempBuffer = new StringBuffer();
		tempBuffer.append("name").append("=").append("jose").append(",");
		String str = tempBuffer.substring(0, tempBuffer.length() - 1);
		System.out.println(str);
		StringBuilder sb = new StringBuilder();
		sb.append("name=").append("josebruce,");
		sb.append("key=").append("AEVR2NO1VJNONRPHLA1DLFUCMLULQD1F");
		String md5Str = sb.toString();
		logger.info(" ************ MD5摘要数据原串【 " + md5Str.substring(0, md5Str.length() - 9) + "*********" + "】 ************ ");
		System.out.println(sb.toString());
		
	}
	
	public static String byteToHexString(byte[] tmp) {
		String s;
		// 用字节表示就是 16 个字节
		char str[] = new char[16 * 2]; // 每个字节用 16 进制表示的话，使用两个字符，
		// 所以表示成 16 进制需要 32 个字符
		int k = 0; // 表示转换结果中对应的字符位置
		for (int i = 0; i < 16; i++) { // 从第一个字节开始，对 MD5 的每一个字节
			// 转换成 16 进制字符的转换
			byte byte0 = tmp[i]; // 取第 i 个字节
			str[k++] = hexdigits[byte0 >>> 4 & 0xf]; // 取字节中高 4 位的数字转换,
			// >>> 为逻辑右移，将符号位一起右移
			str[k++] = hexdigits[byte0 & 0xf]; // 取字节中低 4 位的数字转换
		}
		s = new String(str); // 换后的结果转换为字符串
		return s;
	}	
	/** 
	 * 处理数组中的null值和除去非签名参数
	 * @param sArray 签名参数组
	 * @return 去掉空值与签名参数后的新签名参数组
	 */
	private static Map<String, String> doFilterParam(Map<String, String> params) {
		List<String> keys = new ArrayList<String>(params.keySet());
		Map<String, String> newParam = new HashMap<String, String>();

		for (int i = 0; i < keys.size(); i++) {
			String key = keys.get(i);
			String value = params.get(key);
			
			if ("hmac".equalsIgnoreCase(key) || "signMsg".equalsIgnoreCase(key)
					|| "cert".equalsIgnoreCase(key)) {
				continue;
			}
			if (value == null||value.length()==0) {
				continue;
			}
			newParam.put(key, value);
		}

		return newParam;
	}

	
	/** 
	 * 得到签名源数据
	 * @param sArray 要签名的参数数组
	 * @param key 签名密码
	 * @return 签名源字符串
	 */
	private static String getSignSourMsg(Map<String, String> params, String key) {
		params = doFilterParam(params);
		String signSource = createLinkString(params);
		if (key != null && key.length() > 0) {//证书签名时签名密码为空
			signSource = signSource + "|key=" + key;
		}
		System.out.println("签名原数据："+signSource);
		return signSource;

	}
	/** 
	 * 生成MD5方式签名的签名结果
	 * @param sArray 要签名的数组
	 * @param key 签名密码
	 * @param inputCharset 签名字符集,与请求接口中的字符集一致
	 * @return 签名结果字符串
	 */
	public static String getMd5SignMsg(Map<String, String> params, String key, String inputCharset) {
		//signStr = "";
		logger.info("接入KEE-------------------------:" + key);
		String signSource = getSignSourMsg(params,key);
		String signMsg = HashEncrypt.doEncrypt(signSource, ALG_MD5, inputCharset);
		logger.info("MD5签名："+signMsg);
		return signMsg;
	}
	
	/** 
	 * 把数组所有元素排序，并按照“参数=参数值”的模式用“|”字符拼接成字符串
	 * @param params 需要排序并参与字符拼接的参数组
	 * @return 拼接后字符串
	 */
	private static String createLinkString(Map<String, String> params) {
		List<String> keys = new ArrayList<String>(params.keySet());
		Collections.sort(keys);

		StringBuffer prestr = new StringBuffer("");

		for (int i = 0; i < keys.size(); i++) {
			String key = keys.get(i);
			String value = params.get(key);

			if (i == keys.size() - 1) {// 拼接时，不包括最后一个|字符
				prestr.append(key).append("=").append(value);
			} else {
				prestr.append(key).append("=").append(value).append("|");
			}
		}
		return prestr.toString();//.substring(0,prestr.length()-1)
	}
}
